package top.wuhaojie.library;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.TextAlignment;
import ohos.app.Context;

/**
 * lipeiquan
 *
 * @since 2021-04-22
 */
class ScrollNumber extends Component implements Component.EstimateSizeListener, Component.DrawTask {
    private static final int VIEW_WIDTH = 200;
    private static final int VIEW_HEIGHT_EXCESS = 10;
    private static final double SP2PX_MULTIPLE = 2.6;
    private static final int SP2PX_VAL = 130;
    private static final int DP2PX_VAL = 40;
    private static final int TEXTCOLOR_VAL = 0xFF000000;

    private static final String TAG = "ScrollNumber";
    /**
     * default animation velocity
     */
    private static final int DEFAULT_VELOCITY = 15;
    /**
     * number to - number from
     */
    private int mDeltaNum;
    /**
     * the current showing number
     */
    private int mCurNum;
    /**
     * the next showing number
     */
    private int mNextNum;
    /**
     * the target number
     */
    private int mTargetNum;

    /**
     * number offset
     */
    private float mOffset;
    private Paint mPaint;

    private float mTextCenterX;
    private int mTextHeight;
    private Rect mTextBounds = new Rect();
    private int mTextSize = sp2px(SP2PX_VAL);
    private int mTextColor = TEXTCOLOR_VAL;

    private int mVelocity = DEFAULT_VELOCITY;

    private StringBuffer sb = new StringBuffer();

    /**
     * 构造函数
     *
     * @param context
     */
    public ScrollNumber(Context context) {
        this(context, null);
    }

    /**
     * 构造函数
     *
     * @param context
     * @param attrs
     */
    public ScrollNumber(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * 构造函数
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public ScrollNumber(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(TextAlignment.CENTER);
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(new Color(mTextColor));
        measureTextHeight();
        setEstimateSizeListener(this);
        addDrawTask(this);
        setWidth(VIEW_WIDTH);
        setHeight(mTextHeight + VIEW_HEIGHT_EXCESS);
    }

    public void setVelocity(@IntRange(from = 0, to = 1000) int velocity) {
        mVelocity = velocity;
    }

    public void setNumber(final int from, final int to, long delay) {
        getContext().getUITaskDispatcher().delayDispatch(new Runnable() {
            @Override
            public void run() {
                setFromNumber(from);
                setTargetNumber(to);
                mDeltaNum = to - from;
            }
        }, delay);
    }

    public void setTextSize(int textSize) {
        this.mTextSize = sp2px(textSize);
        mPaint.setTextSize(mTextSize);
        measureTextHeight();
        postLayout();
        invalidate();
    }

    public void setTextFont(String fileName) {
        if (TextUtils.isEmpty(fileName)) {
            throw new IllegalArgumentException("please check file name end with '.ttf' or '.otf'");
        }
        mPaint.setFont(FontUtil.createFontBuild(getContext(), fileName));
        postLayout();
        invalidate();
    }

    public void setTextColor(int textColor) {
        this.mTextColor = textColor;
        mPaint.setColor(new Color(mTextColor));
        invalidate();
    }

    private void measureTextHeight() {
        sb.setLength(0);
        Rect textBounds = mPaint.getTextBounds(sb.append(mCurNum).subSequence(0, 1).toString());
        mTextHeight = textBounds.getHeight();
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);
        setEstimatedSize(width, height);
        mTextCenterX = (getEstimatedWidth() - getPaddingLeft() - getPaddingRight()) >>> 1;

        return false;
    }

    private int measureHeight(int measureSpec) {
        int mode = MeasureSpec.getMode(measureSpec);
        int val = MeasureSpec.getSize(measureSpec);
        int result = 0;
        switch (mode) {
            case MeasureSpec.PRECISE:
                result = val;
                break;
            case MeasureSpec.NOT_EXCEED:
            case MeasureSpec.UNCONSTRAINT:
                sb.setLength(0);
                mPaint.getTextBounds(sb.append("0").substring(0, 1));
                result = mTextBounds.getHeight();
                break;
            default:
                break;
        }
        result = mode == MeasureSpec.NOT_EXCEED ? Math.min(result, val) : result;
        return result + getPaddingTop() + getPaddingBottom() + dp2px(DP2PX_VAL);
    }

    private int measureWidth(int measureSpec) {
        int mode = MeasureSpec.getMode(measureSpec);
        int val = MeasureSpec.getSize(measureSpec);
        int result = 0;
        switch (mode) {
            case MeasureSpec.PRECISE:
                result = val;
                break;
            case MeasureSpec.NOT_EXCEED:
            case MeasureSpec.UNCONSTRAINT:
                sb.setLength(0);
                mPaint.getTextBounds(sb.append("0").substring(0, 1));
                result = mTextBounds.getWidth();
                break;
            default:
                break;
        }
        result = mode == MeasureSpec.NOT_EXCEED ? Math.min(result, val) : result;
        return result + getPaddingLeft() + getPaddingRight() + 15;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (mCurNum != mTargetNum) {
            getContext().getUITaskDispatcher().delayDispatch(mScrollRunnable, 0);
        }
        canvas.translate(0, mOffset * getEstimatedHeight());
        drawSelf(canvas);
        drawNext(canvas);
    }

    private void setFromNumber(int number) {
        if (number < 0 || number > 9)
            throw new RuntimeException("invalidate number , should in [0,9]");
        calNum(number);
        mOffset = 0;
        invalidate();
    }

    private void calNum(int number) {
        number = number == -1 ? 9 : number;
        number = number == 10 ? 0 : number;
        mCurNum = number;
        mNextNum = number + 1 == 10 ? 0 : number + 1;
    }

    private Runnable mScrollRunnable = new Runnable() {
        @Override
        public void run() {
            float x = (float) (1 - 1.0 * (mTargetNum - mCurNum) / mDeltaNum);
            mOffset -= mVelocity * 0.01f * (1 - x + 0.1);
            invalidate();
            if (mOffset <= -1) {
                mOffset = 0;
                calNum(mCurNum + 1);
            }
        }
    };

    private void drawNext(Canvas canvas) {
        float y = (float) (getEstimatedHeight() * 1.5);
        canvas.drawText(mPaint, mNextNum + "", mTextCenterX, y + (float) mTextHeight / (float) 2);
    }

    private void drawSelf(Canvas canvas) {
        int y = getEstimatedHeight() / 2;
        canvas.drawText(mPaint, mCurNum + "", mTextCenterX, y + mTextHeight / 2);
    }

    /**
     * setTargetNumber
     *
     * @param nextNum
     */
    public void setTargetNumber(int nextNum) {
        this.mTargetNum = nextNum;
        invalidate();
    }

    private int dp2px(float dpVal) {
        return (int) dpVal;
    }

    private int sp2px(float dpVal) {
        return (int) (dpVal * SP2PX_MULTIPLE);
    }
}
